/*
 * print.c - Mokri
 * Contributors: xMine, pharno
 * (Any new Contributor may add himself/herself to the list)
 * Changes and fixes to be made: FIXME in der printf_ex dürfen die Werte nicht wieder freigegeben
 *	werden. Ansonsten gibts nach ein paar hundert konvertierungen nen stillstand ohne Fehler!?
 *	Vermutlich ein Bug in der free() (siehe memory.c)...
 */

#include "print.h"

extern volatile char *videoram;

static uint8_t cursor_x = 0;
static uint8_t cursor_y = 0;
static char TextColor = 0x7;

void setTextColor(char color) {
	TextColor = color;
}

char getTextColor () {
	return TextColor;
}

/* Leert den Bildschirm
 * Parameter: Keine
 * Rückgaben: Keine
 */
void clearscreen() {
    cursor_x = cursor_y = 0;

    // Bildschirm leeren
    volatile char *vptr = videoram; // Pointer auf unseren Videoram
    for (; vptr < (char*)(videoram+VRAM_SIZE); vptr++)
        *vptr = 0x0;

    /*
    // Buffer leeren
    vptr = videobuffer;
    for (; vptr < videobuffer+VRAM_SIZE; vptr++)
        *vptr = 0x0;
    */
}

/* Scrollt den Bildschirm nach oben
 * Parameter: Zeilen
 * Rückgaben: Keine
 */
void scrollscreen (int zeilen) {
    if (zeilen < 0) return;
    if (zeilen >= SCREEN_HEIGHT) { // Anti-der-Coder-war-zu-faul-if-Anweisung ...
        clearscreen ();
        return;
    }

    #if 0
    memcpy (videobuffer, videobuffer+(zeilen*SCREEN_WIDTH*2), SCREEN_SIZE-(zeilen*SCREEN_WIDTH*2)); // Bildschirm inhalt "nach oben kopieren"
    memset (videobuffer+(SCREEN_SIZE-(zeilen*SCREEN_WIDTH*2)), 0x0, zeilen*SCREEN_WIDTH*2); // Rest des Bildschirms leeren
    printscreen ();
    #else
    memcpy ((void*)videoram, (void*)videoram+(zeilen*SCREEN_WIDTH*2), SCREEN_SIZE-(zeilen*SCREEN_WIDTH*2)); // Bildschirm inhalt "nach oben kopieren"
    memset ((void*)videoram+(SCREEN_SIZE-(zeilen*SCREEN_WIDTH*2)), 0x0, zeilen*SCREEN_WIDTH*2); // Rest des Bildschirms leeren
    #endif
    cursor_y -= zeilen;
    cursor_x = 0;
}

/* Schreibt den Buffer in den Video RAM
 * Parameter: Keine
 * Rückgaben: Keine
 */
void printscreen() {
    #if 0
    volatile char *video = videoram;
    char *ptr = videobuffer;
    for (;video < (char*)(VRAM_POS+VRAM_SIZE); video++, ptr++) {
        *video = *ptr;
    }
    #endif
}

void putchar (char c, short pos_x, short pos_y) {
    int offset = (pos_y*SCREEN_WIDTH+pos_x)*2;

    //videobuffer [offset] = c; // Zeichen
    //videobuffer [offset+1] = TextColor; // Farbe
    videoram [offset] = c;
    videoram [offset+1] = TextColor;
}

/* Schreibt einen char-Array in den Video RAM
 * Parameter: Ausgabestring, sofort ausgeben
 * Rückgaben: Keine
 */
void print_ex (const char *str) {
    uint32_t i;
    
    char col = getTextColor ();
    
    for (i=0; str[i]; i++) { // Ende eines Strings = 0x0. False = 0x0 :)
        if (cursor_x >= SCREEN_WIDTH) { // Zeile voll?
            cursor_x = 0;
            cursor_y++;
        }

        if (cursor_y >= SCREEN_HEIGHT) {
		scrollscreen (SCREEN_HEIGHT-cursor_y+1);
        }

        // Steuerzeichen?
        if (str[i] == '\n') { // Line-Feed
            cursor_x = 0;
            cursor_y++;
        } else if (str[i] == '\r') { // Carriage-Return
            cursor_x = 0;
        } else if (str[i] == '\t') { // Tabulator
	    cursor_x += 4;
	} else if (str[i] == '\b') { // Backspace
		cursor_x--;
		if (cursor_x < 0) {
			if (cursor_y > 0) {
				cursor_x = SCREEN_WIDTH-1;
				cursor_y--;
			} else {
				cursor_x = 0;
			}
		}
		
		putchar (' ', cursor_x, cursor_y);

        } else if (str[i] >= (char)14 && str[i] <= (char)31) { // Farbe
		setTextColor (str[i]-(char)14);
	} else { // Kein Steuerzeichen
            putchar (str[i], cursor_x, cursor_y); // Ausgeben
            cursor_x++;
        }
    }
    
    setTextColor (col);
    //vga_move_cursor (cursor_x, cursor_y);
}

/* Schreibt einen char-Array in den Video RAM
 * Parameter: Ausgabestring
 * Rückgaben: Keine
 */
void print(const char *str) {
    print_ex (str);
}

/*
 * Gibt einen formatierten String aus .... es ist printf verdammt!
 * Unterstützte Formatbezeichner: %i, %d, %u, %p, %c, %s, %x, %o
 * Parameter: Ausgabestring, ...
 * Rückgaben: Ausgegebene Bytes
 */
int printf (const char *str, ...) {
	va_list liste;
	va_start (liste, str);
	int ret = printf_ex (str, &liste);
	va_end (liste);
	return ret;
}

int printf_ex (const char *str, va_list *args) {
	char ausgabe[1024]; // TODO - Irgendwie variabel viele Zeichen holen
	char tmp[32];

	int i, j, len;
	char var;
	
	int arg; // Parameter für die Formatbezeichner
	char filler; 
	
	for (i=0, j=0; str[i]; i++) { // i = Position in str, j = Position in ausgabe
		if (str[i] == '%') { // Variable behandeln?
			
			var = str[i+1];
			arg = 0;
			filler = ' ';
			memset (tmp, 0x0, 32);
			
			// Sind Flags enthalten?
			if ( (var >= '0' && var <= '9') || var == '#' ) {
				char flags[16];
				memset (flags, 0x0, 16);
				char *ende = strfind ((char*)(str+i+1), 'a', 'z'); // Ende suchen
				
				if (ende == NULL || ende > str+i+15) { // Wenn nichts gefunden wurde einfach das Zeichen ausgeben
					ausgabe[j] = '%';
				} else {
					memcpy (flags, str+i+1, ende-(str+i+1)); // Flags kopieren
					
					int k, l2 = strlen(flags);
					for (k=0; k<l2; k++) {
						if (k == 0 && flags[k] == '0') {
							filler = '0';
						} else { // TODO: Hier funktionieren nur einstellige Zahlen
							arg = (int)(flags[k]-'0');
						}
					}
				}
				
				fill (filler, arg, tmp); // Füller erstellen
				var = *ende; 		 // Formatbezeichner neu setzen
				i += strlen(flags);	 // Damit werden die Flags übersprungen
			}
			
			if (var == '%') { // % Ausgeben
				ausgabe[j] = '%';
				j++;
			} else if (var == 'i' || var == 'd') { // Integer ausgeben
				int zahl = va_arg (*args, int); // Zahl vom Stack holen
				char *zahlstr = int2str (zahl, 10); // In einen String umwandeln
				len = strlen(zahlstr);
				arg -= len;
				if (arg > 0) {
					memcpy (ausgabe+j, tmp, arg);
					j += arg;
				}
				memcpy (ausgabe+j, zahlstr, len);
				j += len;
				free (zahlstr);
			}  else if (var == 'u') {
				unsigned int zahl = va_arg (*args, unsigned int);
				char *zahlstr = uint2str ((uint64_t)zahl, 10);
				len = strlen(zahlstr);
				arg -= len;
				if (arg > 0) {
					memcpy (ausgabe+j, tmp, arg);
					j += arg;
				}
				memcpy (ausgabe+j, zahlstr, len);
				j += len;
				free (zahlstr);
			}  else if (var == 'p' || var == 'x') {
				void *ptr = va_arg (*args, void*);
				char *zahlstr = uint2str ((uint64_t)((uint32_t)ptr), 16);
				len = strlen(zahlstr);
				arg -= len;
				if (arg > 0) {
					memcpy (ausgabe+j, tmp, arg);
					j += arg;
				}
				memcpy (ausgabe+j, zahlstr, len);
				j += len;
				free (zahlstr);
			} else if (var == 'o') { // Oktale ausgabe
				unsigned int zahl = va_arg (*args, unsigned int);
				char *zahlstr = uint2str ((uint64_t)zahl, 2);
				len = strlen (zahlstr);
				arg -= len;
				if (arg > 0) {
					memcpy (ausgabe+j, tmp, arg);
					j += arg;
				}
				memcpy (ausgabe+j, zahlstr, len);
				j += len;
				free (zahlstr);
			} else if (var == 's') { // String ausgeben
				const char *eingabe = va_arg (*args, const char*);
				len = strlen(eingabe);
				
				if (len > 1024-j-1) { // String ist zu groß fürn Zwischenspeicher
					ausgabe[j] = 0x0;
					print (ausgabe);
					print (eingabe);
					j = 0;
				} else {
					memcpy (ausgabe+j, eingabe, len);
					j += len;
				}
			} else if (var == 'c') { // Charakter ausgeben
				char eingabe = (char)va_arg (*args, int);
				ausgabe[j] = eingabe;
				j++;
			}

			i++; // Nächstes Zeichen überspringen
		} else {
			ausgabe[j] = str[i];
			j++;
		}
		
		if (j > 1000) {
			ausgabe[j] = 0x0;
			print (ausgabe);
			j = 0;
		}
	}

	ausgabe [j] = 0x0;
	print (ausgabe); // ausgeben
	
	return j;
}
